#include <ctype.h>
#include <err.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "../defer.h"
#include "../util.h"

/* NOLINTBEGIN */
#define BUFF_SIZE 255
#define KEY_MAX	  12 /* MAX(LEN("vendor_id"), LEN("model name")) +1 */
/* NOLINTEND */

enum CPUINFO {
	VENDOR_ID,
	MODEL_NAME,

	COUNT_CPUINFO,
};

static char *cpuinfo(enum CPUINFO type);

char *
get_processor(void)
{
#ifdef __linux__
	return cpuinfo(MODEL_NAME);
#endif
	return NULL;
}

char *
get_hw_platform(void)
{
#ifdef __linux__
	return cpuinfo(VENDOR_ID);
#endif
	return NULL;
}

static inline int
seek_to(FILE *fptr, int ch)
{
	int c; /* NOLINT(readability-identifier-length) */

	do {
		c = fgetc(fptr);
	} while (c != ch && c != EOF);

	return c;
}

#define NEW_CpuInfoEntry(S)                                                   \
	{                                                                     \
		.key = (S), .value = { 0 }, .found = 0                        \
	}
typedef struct CpuInfoEntry {
	const char *key;
	char	    value[BUFF_SIZE];
	uint8_t	    found;
} __attribute__((aligned(128))) CpuInfoEntry;

static inline char *
cached_info(CpuInfoEntry *entry)
{
	DEBUG_ASSERT(!!entry);
	if (entry->found) return entry->value;
	return NULL;
}

typedef struct Key {
	char data[KEY_MAX];
} __attribute__((aligned(16))) Key;

/* if value present or key unmatched fptr is not touched */
static char *
try_get_value(FILE *fptr, const Key key, CpuInfoEntry *entry)
{
	char *value = cached_info(entry);
	if (value) return value;

	if (STRNEQ(key.data, entry->key, sizeof(key.data))) {
		if (fgetc(fptr) == EOF) return NULL; /* skip space after ':' */
		if (fscanf(fptr, "%" STR(BUFF_SIZE) "[^\n]s", entry->value)
		    == EOF)
			return NULL;

		entry->found = !0;

		return entry->value;
	}
	return NULL;
}

/* if all enties found return true */
static uint8_t
try_get_all_values(FILE *fptr, const Key key, CpuInfoEntry *enties, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		if (!try_get_value(fptr, key, &enties[i])) return 0;
	}
	return !0;
}

static inline char *
rstrip(char *str)
{
	size_t len = strlen(str);
	if (!len) return str;

	char *end = str + len - 1;
	while (end >= str && isspace(*end)) {
		*end-- = '\0';
	}

	return str;
}

static char *
cpuinfo(enum CPUINFO type)
{
	Deferral;

	static CpuInfoEntry name_vendor[COUNT_CPUINFO] = {
		[VENDOR_ID]  = NEW_CpuInfoEntry("vendor_id"),
		[MODEL_NAME] = NEW_CpuInfoEntry("model name"),
	};

	CpuInfoEntry *entry = NULL;
	switch (type) {
	case VENDOR_ID: /* fallthrough */
	case MODEL_NAME:
		entry = &name_vendor[type];
		break;
	default:
		UNREACHABLE();
	}

	char *value = cached_info(entry);
	if (!!value) return value;

	FILE *fptr = fopen("/proc/cpuinfo", "re");
	if (!fptr) {
		warn("%s", "/proc/cpuinfo");
		return NULL;
	}
	Defer((void)fclose(fptr));

	Key key;
	while (fscanf(fptr, "%" STR(KEY_MAX) "[^:]s", key.data) != EOF) {
		if (seek_to(fptr, ':') == EOF) goto end;
		rstrip(key.data);
		if (try_get_all_values(fptr,
				       key,
				       name_vendor,
				       LEN(name_vendor)))
			break;
		if (seek_to(fptr, '\n') == EOF) goto end;
	}

end:
	Return cached_info(entry);
}
